<template>
  <div>


    <el-dialog :visible="visible" title="上传" width="800px" @close="cancelClick">
      <el-upload action="" :on-change="selectFile" :auto-upload="false">

        <el-button type="primary" slot="trigger">选取文件</el-button>
        <el-button type="primary" @click="onUpload" style="margin-left: 10px;"> 上传文件 </el-button>
      </el-upload>

      {{ state.dataSource }}
      <div v-for="item in state.dataSource" :key="item.uid">
        <div>
          {{ item.name }}
        </div>
        <div>
          <div>
            大小: <span style="color: blue">{{ item.unitSize }}</span>
          </div>
          <div>
            md5:
            <span style="color: red">
              <template v-if="item.md5Progress">
                {{ `${item.md5Progress}%` }}
              </template>
              <template v-else>{{ item.md5 }}</template>
            </span>
          </div>

        </div>

        <el-tag :color="tagMap[item.status].color">{{ tagMap[item.status].text }}</el-tag>

      </div>

    </el-dialog>


    <!-- <a-modal title="上传" :width="800" :visible="visible" @cancel="cancelClick">
      <a-card title="Arco Card" size="small">
        <template #title>
          <a-space>
            <a-upload :show-file-list="false" :auto-upload="false" @change="selectFile" />
            <a-button type="primary" @click="onUpload">
              <template #icon><icon-cloud /></template>
<template #default>上传文件</template>
</a-button>
</a-space>
</template>

<a-list size="small">
  <a-list-item v-for="item in state.dataSource" :key="item.uid">
    <a-list-item-meta :title="item.name">
      <template #description>
                <a-space size="medium">
                  <div>
                    大小: <span style="color: blue">{{ item.unitSize }}</span>
                  </div>
                  <div>
                    md5:
                    <span style="color: red">
                      <template v-if="item.md5Progress">
                        {{ `${item.md5Progress}%` }}
                      </template>
      <template v-else>{{ item.md5 }}</template>
      </span>
      </div>
      </a-space>
      <a-progress v-if="item.progress" :percent="item.progress / 100" />
      </template>
    </a-list-item-meta>
    <template #actions>
              <a-tag :color="tagMap[item.status].color">{{ tagMap[item.status].text }}</a-tag>
            </template>
  </a-list-item>
</a-list>
</a-card>
</a-modal> -->
  </div>


</template>



<script>

import axios from 'axios'
import pLimit from 'p-limit'
import { CHUNK_SIZE, HttpCodeUploadEnum, convertFileSizeUnit } from '@/utils/fileUtil.js'
// import createChunkFileAndMd5 from '../util/createChunkFileAndMd5'

import { checkFileByMd5, initMultPartFile, mergeFileByMd5 } from '@/api/upload'


import cutFile from '@/core/cutFile'
import { MerkleTree } from '@/core/MerkleTree'
import { reactive } from 'vue'
//  文件上传过程中的多种状态

export default {
  name: 'FileTable',
  components: {
  },
  data() {
    return {
      ChunkFileUrlType: {
        url: ''
      },
      limit: pLimit(3),
      state: {
        dataSource: []
      },
      tagMap: {
        preparation: { color: 'gold', text: 'MD5计算中' },
        preupload: { color: 'purple', text: '等待上传' },
        uploading: { color: 'blue', text: '上传中' },
        success: { color: 'green', text: '上传成功' },
        error: { color: 'error', text: '上传失败' }
      }
    }
  },
  props: {
    visible: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    visible: {
      handler(value) {

      }
    }
  },
  methods: {
    // 关闭弹窗
    cancelClick() {
      this.state.dataSource = []
      this.$emit('update:visible', false)
    },
    // 选择文件并计算 md5
    async selectFile(_, fileItem) {
      console.log(_, fileItem, '_, fileItem');
      const file = _.raw
      if (!file) return

      const chunkCount = Math.ceil((file.size ?? 0) / CHUNK_SIZE)
      // 展示给 table的数据，部分参数用于初始化
      const dataItem = {
        uid: fileItem.uid,
        name: file.name,
        size: file.size ?? 0,
        unitSize: convertFileSizeUnit(file.size),
        md5: '',
        md5Progress: 0,
        progress: 0,
        chunkCount,
        file: file,
        status: 'preparation',
        chunkFileList: [],
        uploadedSize: 0
      }
      this.state.dataSource.push(dataItem)
      console.log(this.state.dataSource, '1');
      const i = this.state.dataSource.findIndex((item) => item.uid == dataItem.uid)
      // 同步计算分片文件和 md5，实时更新计算进度
      // const { md5, chunkFileList } = await createChunkFileAndMd5(
      //   file as RcFile,
      //   chunkCount,
      //   (progress) => {
      //     this.state.dataSource[i].md5Progress = progress
      //   },
      // )

      // 采用多线程计算和默克尔树计算树根
      const chunks = await cutFile(file)
      const merkleTree = new MerkleTree(chunks.map((chunk) => chunk.hash))
      const md5 = merkleTree.getRootHash()
      const chunkFileList = chunks.map((chunk) => chunk.blob)
      console.log(md5, chunkFileList, '这里没计算好')

      // 更新数据和状态
      this.state.dataSource[i] = {
        ...this.state.dataSource[i],
        md5,
        chunkFileList,
        status: 'preupload'
      }
    },

    // 查询文件状态并上传
    async onUpload() {
      for (let i = 0; i < this.state.dataSource.length; i++) {
        // md5 未计算完成和正在上传的跳过（重复点击的情况）
        if (!this.state.dataSource[i].md5 || this.state.dataSource[i].status == 'uploading') continue

        await uploadFile(i, this.state.dataSource[i])
      }
    },

    /**
     * 上传处理方法
     * @param index 如果直接修改 item，在上传过程中，item一直在被更改，而循环传入的 item 却一直是初始值，因此需要 index 确定当前 item 的最新值
     * @param item
     */
    async uploadFile(index, item) {
      const { code, data } = await checkFileByMd5(item.md5)
      this.state.dataSource[index].status = 'uploading'

      if (code === HttpCodeUploadEnum.SUCCESS) {
        //  上传成功
        this.state.dataSource[index].progress = 100
        this.state.dataSource[index].status = 'success'
        return
      } else if (code === HttpCodeUploadEnum.FAIL) {
        //  上传失败
        this.state.dataSource[index].status = 'error'
        return
      } /*  else if (code === HttpCodeUploadEnum.UPLOADING) {
        // 上传中，返回已上传的文件数据和分片列表
      } else {
        // 未上传
      } */

      // 返回需要上传分片和对应地址
      const needUploadFile = await initSliceFile(item, data)
      console.log('需要上传的文件', needUploadFile)
      const totalSize = needUploadFile.reduce((pre, cur) => pre + cur.file.size, 0)

      // plimit 并发上传
      const uploadLimit = needUploadFile.map((n) =>
        limit(() => uploadChunkUrl(n, index, totalSize, item.file.type))
      )

      const results = await Promise.allSettled(uploadLimit)
      const errResults = results.filter((r) => r.status === 'rejected')

      if (errResults.length > 0) {
        console.warn(item.name + ' 上传失败的分片-----', errResults)
        this.state.dataSource[index].status = 'error'
        return
      }

      try {
        const { code, data } = await mergeFileByMd5(item.md5)
        if (code === 200) {
          console.log(data)
          this.state.dataSource[index].status = 'success'
          this.state.dataSource[index].progress = 100
        }
      } catch (error) {
        this.state.dataSource[index].status = 'error'
      }
    }
    ,
    // 初始化分片操作并将分片文件和其上传地址一一对应
    async initSliceFile(item, initData) {
      //  只有上传中的分片文件才会有 initData 数据，用 {} 做兜底
      const { uploadId, listParts } = initData || {}

      // 初始化分片参数
      const param = {
        uploadId,
        originFileName: item.name,
        size: item.size,
        chunkSize: CHUNK_SIZE,
        chunkCount: item.chunkCount,
        md5: item.md5,
        contentType: item.file.type
      }

      const needUploadFile = []

      const { code, data } = await initMultPartFile(param)
      if (code !== 200) return []

      // 存放需要去上传的文件数据
      if ((listParts || []).length == 0) {
        // 若全都没有上传，一一对应，其中 urls 是所有分片上传的 url 集合
        item.chunkFileList.forEach((item, index) => {
          needUploadFile.push({ url: data.urls[index], file: item })
        })
        return needUploadFile
      }

      // 存在上传的，对比 minio 已上传的 listParts（序号），将已上传的过滤掉，只上传未上传的文件
      item.chunkFileList.forEach((item, index) => {
        // listParts 索引是从 1 开始的
        const i = (listParts || []).findIndex((v) => index + 1 == v)
        if (i === -1) {
          needUploadFile.push({ url: data.urls[index], file: item })
        }
      })

      return needUploadFile
    }
    ,
    // 根据分片上传地址将分片直传至 minio
    uploadChunkUrl(
      chunkItem,
      i,
      totalSize,
      type
    ) {
      return new Promise((resolve, reject) => {
        axios
          .put(chunkItem.url, chunkItem.file, {
            headers: { 'Content-Type': type || 'application/octet-stream' }
          })
          .then((res) => {
            if (res.status !== 200) {
              reject(chunkItem)
            } else {
              // 已上传的文件大小更新，上传进度更新
              const newUploaedSize = this.state.dataSource[i].uploadedSize + chunkItem.file.size
              this.state.dataSource[i] = {
                ...this.state.dataSource[i],
                uploadedSize: newUploaedSize,
                progress: Math.floor((newUploaedSize / totalSize) * 100)
              }
              resolve()
            }
          })
          .catch((err) => {
            console.error(err)
            reject(chunkItem)
          })
      })
    }

  }
}






</script>
